// 
// SoapTrace.Net - A soap message monitoring utility. 
// 
// Copyright (C) 2004-2009 Matthew Ward
// 
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
// 
// Matthew Ward (mrward@users.sourceforge.net)
// 

using NUnit.Framework;
using SoapTrace.Core.Communication;
using SoapTrace.Tests.Utils;
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace SoapTrace.Tests
{
	/// <summary>
	/// Proxy server test fixture.
	/// </summary>
	[ TestFixture ]
	public class ProxyServerTestFixture
	{
		/// <summary>
		/// The proxy server object that will be tested by this test fixture.
		/// </summary>
		private ProxyServer m_ProxyServer = null;
		
		/// <summary>
		/// Mock remote server that the proxy server will connect to and
		/// interact with.
		/// </summary>
		private MockRemoteServer m_MockRemoteServer = null;
		
		/// <summary>
		/// The proxy server's IP address.
		/// </summary>
		private string m_ProxyServerIPAddress = "localhost";
		
		/// <summary>
		/// The proxy server's listening port.
		/// </summary>
		private int m_ProxyServerPort = 7001;
		
		/// <summary>
		/// The remote server's IP address.
		/// </summary>
		private string m_RemoteServerIPAddress = "localhost";
		
		/// <summary>
		/// The remote server's port.
		/// </summary>
		private int m_RemoteServerPort = 8001;
		
		/// <summary>
		/// The timeout in milliseconds for proxy server actions to 
		/// complete.
		/// </summary>
		private int m_WaitTimeout = 5000;
				
		/// <summary>
		/// The client used to communicate with the remote server.
		/// </summary>
		private MockRemoteServerClient m_Client = new MockRemoteServerClient( );
				
		/// <summary>
		/// Initialises each test.
		/// </summary>
		[ SetUp ]
		public void Init( )
		{
			// Create dummy remote server object.
			m_MockRemoteServer = new MockRemoteServer( );
			m_MockRemoteServer.Start( m_RemoteServerPort );
	
			// Start the proxy server.
			m_ProxyServer = new ProxyServer( );
			m_ProxyServer.Start( m_ProxyServerPort,
			                     m_RemoteServerIPAddress,
			                     m_RemoteServerPort );
		}
		
		/// <summary>
		/// Clears up after each test.
		/// </summary>
		[ TearDown ]
		public void TearDown( )
		{
			m_ProxyServer.Stop( );
			m_MockRemoteServer.Stop( );
			m_Client.Close( );
		}
		
		/// <summary>
		/// Tests that a client can connect to a proxy server and that
		/// the proxy server connects through to the server it is proxying.
		/// </summary>
		[ Test ]
		public void ConnectClient( )
		{
			// Connect a tcp client to the proxy.
			m_Client.Connect( m_ProxyServerIPAddress, m_ProxyServerPort );
			
			// Ensure the client connected to the remote server.
			bool Success = m_MockRemoteServer.WaitForConnection( m_WaitTimeout );
			
			Assert.IsTrue( Success, 
			               "Proxy did not connect to remote server." );	
		}
		
		/// <summary>
		/// Tests that a client disconnection is passed on to the remote
		/// server through the proxy.
		/// </summary>
		[ Test ]
		public void DisconnectClient( )
		{
			ConnectClient( );
			
			m_Client.Close( );
			
			// Ensure the client disconnection has been forwarded
			// to the remote server.
			
			bool Success = 
				m_MockRemoteServer.WaitForDisconnection( m_WaitTimeout );
			
			Assert.IsTrue( Success,
			              "Proxy did not forward disconnect to remote server." );
		}
		
		/// <summary>
		/// Tests that a message sent from a client is received by
		/// the remote server.
		/// </summary>
		[ Test ]
		public void SendClientMessage( )
		{
			ConnectClient( );
						
			// Send a message from the client to the server.
			string Message = "Hello";
			m_Client.Send( Message );
			
			// Wait for the server to receive the message.
			string MessageArrived = String.Empty;
			bool Success = m_MockRemoteServer.WaitForMessage( out MessageArrived,
				m_WaitTimeout );
			
			Assert.IsTrue( Success, "Timed out waiting for client message" );
			Assert.AreEqual( Message,
			                 MessageArrived,
			                 "Message received does not match that sent." );
		}
		
		/// <summary>
		/// Tests that no clients can connect to the proxy when it is
		/// stopped.
		/// </summary>
		[ Test ]
		[ ExpectedException( typeof( SocketException ),
			ExpectedMessage = "No connection could be made because the target machine actively refused it 127.0.0.1:7001" ) ]
		public void StopProxy( )
		{
			m_ProxyServer.Stop( );
			
			ConnectClient( );
		}
		
		/// <summary>
		/// Tests to see if stopping the remote server causes a disconnect
		/// on the client.
		/// </summary>
		[ Test ]
		public void StopRemoteServer( )
		{
			ConnectClient( );
			
			// Stop the remote server.
			
			m_MockRemoteServer.Stop( );
			
			// Wait for the server to disconnect the client.
			
			bool Success = WaitForServerDisconnection( );

			Assert.IsTrue( Success,
			               "Timed out waiting for server disconnection." );
		}
		
		/// <summary>
		/// Tests that a server message is delivered to the client through
		/// the proxy.
		/// </summary>
		[ Test ]
		public void ReceiveRemoteServerMessage( )
		{
			Debug.WriteLine( "Test ReceiveRemoteServerMessage starting" );
			
			ConnectClient( );
					
			// Send the message to connected clients.

			string Message = "Goodbye";

			m_MockRemoteServer.SendMessage( Message );
			
			// Wait until the client receives the message.

			string MessageReceived = String.Empty;
			bool ReadData = m_Client.Read( out MessageReceived, m_WaitTimeout );
			
			Assert.IsTrue( ReadData, 
			               "Server closed client connection." );
			
			Assert.AreEqual( Message,
			                 MessageReceived,
			                 "Message received does not match that sent." );

		}
		
		/// <summary>
		/// Tests that a large message sent from a client is received by
		/// the remote server.  The message is large enough to not be 
		/// able to be read in one socket call.
		/// </summary>
		[ Test ]
		public void SendLargeClientMessage( )
		{
			ConnectClient( );
						
			// Send a message from the client to the server.
			string Message = GetLargeMessage( );
			m_Client.Send( Message );
			
			// Wait for the server to receive the message.
			string MessageArrived = String.Empty;
			bool Success = m_MockRemoteServer.WaitForMessage( out MessageArrived,
				m_WaitTimeout );
			
			Assert.IsTrue( Success, "Timed out waiting for client message" );
			Assert.AreEqual( Message,
				MessageArrived,
				"Message received does not match that sent." );
		}

		/// <summary>
		/// Tests that the client gets disconnected from the proxy server
		/// if the remote server is not running (i.e. the proxy server
		/// cannot connect to it).
		/// </summary>
		[ Test ]
		public void RemoteServerNotRunning( )
		{
			m_MockRemoteServer.Stop( );

			// Connect a tcp client to the proxy.
			m_Client.Connect( m_ProxyServerIPAddress, m_ProxyServerPort );

			// Read data from the socket so we will get the fact
			// that we're disconnected.
			string Message;
			bool MessageReceived = m_Client.Read( out Message, m_WaitTimeout );

			Assert.AreEqual( false, m_Client.IsConnected, "Should not be connected." );
		}

		/// <summary>
		/// Checks that the client recieves any data from the server that the
		/// proxy has recieved even if the server is then disconnected.
		/// </summary>
		[ Test ]
		public void StopServerAfterDataSentFromServer( )
		{
			string Message = "Hello";
			
			ConnectClient( );
			
			m_MockRemoteServer.SendMessage( Message );
			m_MockRemoteServer.Stop( );
			
			// Read data from the socket so we will get the fact
			// that we're disconnected.
			string MessageRead;
			bool MessageReceived = m_Client.Read( out MessageRead, m_WaitTimeout );
			
			Assert.IsTrue( MessageReceived, "Should have received the server message." );
			Assert.AreEqual( Message, MessageRead, "Message mismatch." );
		}
		
		/// <summary>
		/// Waits for the server to disconnect the client.
		/// </summary>
		/// <returns>A status flag whether the server did actually 
		/// disconnect the client or timed out.</returns>
		private bool WaitForServerDisconnection( )
		{
			string MessageReceived = String.Empty;
			
			bool DataRead = m_Client.Read( out MessageReceived, m_WaitTimeout );
			
			bool Closed = !DataRead;

			return Closed;
		}

		/// <summary>
		/// Gets a large message.  The message returned will be larger than
		/// the internal read buffer used in the TcpConnection class (4096 bytes).
		/// </summary>
		/// <returns></returns>
		private string GetLargeMessage( )
		{
			StringBuilder Message = new StringBuilder( );
			
			int IterationCount = 0;

			while( Message.ToString( ).Length < 4096 )
			{
				Message.Append( String.Concat( IterationCount.ToString( ),
					" This is a very big message." ) );
			
				++IterationCount;
			}

			return Message.ToString( );
		}

	}
}
